package ucl.cs.stockbroker.client;

/**
 * StockBroker Client Class.
 * @author  S. Ahmed, K. Olesiejuk, M. Park
 * @version 1.0
 */

// TODO readme
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;

import ucl.cs.stockbroker.common.*;

public class StockBrokerClient {
	private static String serverAddress;
	private static int serverRegistryPort;
	private static StockBroker broker;

	private static StockBrokerAccount getAccount(int acctNo)
			throws RemoteException, NoSuchAccountException {
		return broker.getAccount(acctNo);
	}

	private static void actBuyStock() throws RemoteException {
		try {
			System.out.println("Current registered stock:");
			actShowRegisteredStock();
			System.out.println("Please input the account number");
			// final StockBrokerAccount acct = StockBrokerClient.broker
			// .getAccount(parseInputInt()); - FIXING FOR CHECKING IF TERMINATED
			int acctNo = parseInputInt();
			System.out.println(getAccount(acctNo).getDescription());
			System.out.println("Please input the account PIN");
			final String pin = validateInputPIN();
			if (pin.length() == 0) {
				System.out.println("No input - cancelling operation");
				return;
			} // Allowing cancelling
			System.out.println("Please input the desired company symbol");
			final String symbol = getInput().toUpperCase();
			if (symbol.length() == 0) {
				System.out.println("No input - cancelling operation");
				return;
			} // Allowing cancelling
			System.out.println("How many shares would you like to buy?");
			int units = parseInputInt();
			if (units == -1) {
				System.out.println("No input - cancelling operation");
				return;
			} // Allowing cancelling
			getAccount(acctNo).buyStock(pin, symbol, units);
			System.out.println("Account state after buying:");
			System.out.println(getAccount(acctNo).getDescription());
		} catch (final NoSuchAccountException e) {
			System.out.println("ERROR:No such account.");
			// e.printStackTrace();
		} catch (final WrongPINException e) {
			System.out.println("ERROR:Wrong PIN number.");
			// e.printStackTrace();
		} catch (final NotEnoughMoneyException e) {
			System.out.println("ERROR:Not enough money.");
			// e.printStackTrace();
		} catch (final NoSuchCompanyException e) {
			System.out.println("ERROR:No such company in the database.");
			// e.printStackTrace();
		}
	}

	private static void actCreateAccount() throws RemoteException {
		System.out.println("Please input the desired name of the account");
		String acctName;
		while (true) {
			acctName = getInput();
			if (acctName.trim().length() == 0) {
				System.out.println("Account name cannot only contain spaces");
				continue;
			}
			break;
		}
		if (acctName.length() == 0) {
			System.out.println("No input - cancelling operation");
			return;
		} // Allowing cancelling
		System.out.println("Please input the desired PIN for the account");
		final String PIN = validateInputPIN();
		if (PIN.length() == 0) {
			System.out.println("No input - cancelling operation");
			return;
		} // Allowing cancelling
		final int accountNumber = StockBrokerClient.broker.createAccount(
				acctName, PIN);
		System.out.println("------------------------------------------------");
		System.out.println("The account has been created");
		System.out.println("The account number is : " + accountNumber);
		System.out.println("------------------------------------------------");
	}

	private static void actListAllAccounts() throws RemoteException {
		final StockBrokerAccountInfo[] accts = StockBrokerClient.broker
				.listAccounts();
		for (final StockBrokerAccountInfo acct : accts)
			System.out.println(acct.toString());
	}

	private static void actSellStock() throws RemoteException {
		try {
			System.out.println("Please input the account number");
			// final StockBrokerAccount acct = StockBrokerClient.broker
			// .getAccount(parseInputInt());
			int acctNo = parseInputInt();
			System.out.println(getAccount(acctNo).getDescription());
			System.out.println("Please input the account PIN");
			final String pin = validateInputPIN();
			if (pin.length() == 0) {
				System.out.println("No input - cancelling operation");
				return;
			} // Allowing cancelling
			System.out.println("Please input the desired company symbol");
			final String symbol = getInput().toUpperCase();
			if (symbol.length() == 0) {
				System.out.println("No input - cancelling operation");
				return;
			} // Allowing cancelling
			System.out.println("How many shares would you like to sell?");
			final int units = parseInputInt();
			if (units == -1) {
				System.out.println("No input - cancelling operation");
				return;
			} // Allowing cancelling
			getAccount(acctNo).sellStock(pin, symbol, units);
			System.out.println("Account state after selling:");
			System.out.println(getAccount(acctNo).getDescription());
		} catch (final NoSuchAccountException e) {
			System.out.println("No such account.");
		} catch (final WrongPINException e) {
			System.out.println("ERROR:Wrong PIN number.");
			// e.printStackTrace();
		} catch (final NoSuchCompanyException e) {
			System.out.println("ERROR:No such company in the database.");
			// e.printStackTrace();
		} catch (final NotEnoughStockException e) {
			System.out
					.println("ERROR:Not enough shares/You don't own any shares of this company");
			// e.printStackTrace();
		}
	}

	private static void actShowAccountInfo() throws RemoteException {
		System.out.println("Please input the account number");
		try {
			System.out.println(getAccount(parseInputInt()).getDescription());
		} catch (final NoSuchAccountException e) {
			System.out.println("No such account.");
		}
	}

	private static void actShowRegisteredStock() throws RemoteException {
		final ArrayList<StockCompanyInfo> data = StockBrokerClient.broker
				.getRegisteredStock();
		/*
		 * for (final StockCompanyInfo company : data)
		 * System.out.println("Company: " + company.getName() +
		 * "\nCompany Symbol: " + company.getCompanySymbol() +
		 * "\nCurrent Price: " + company.getStockPrice() + "\n");
		 */
		int rows = data.size();
		String[][] table = new String[rows + 1][3];
		int row = 1;
		table[0][0] = "Company";
		table[0][1] = "Symbol";
		table[0][2] = "Price";
		for (StockCompanyInfo company : data) {
			table[row][0] = company.getName();
			table[row][1] = company.getCompanySymbol();
			table[row][2] = "" + company.getStockPrice() + "p";
			row++;
		}
		printTable(table);
	}

	private static void actTerminateAccount() throws RemoteException {
		System.out.println("Please input the account number");
		final int acctNo = parseInputInt();
		if (acctNo == -1) {
			System.out.println("No input - cancelling operation");
			return;
		} // Allowing cancelling
		System.out.println("Please input the PIN number (4 digits)");
		final String PIN = validateInputPIN();
		if (PIN.length() == 0) {
			System.out.println("No input - cancelling operation");
			return;
		} // Allowing cancelling
		try {
			final double finalBalance = StockBrokerClient.broker
					.terminateAccount(acctNo, PIN);
			System.out.println("Account #" + acctNo + " has been terminated");
			System.out.println("The final balance of your account is: "
					+ finalBalance+"p");
			if(finalBalance>0)
			{
				System.out.println("The StockBroker owes you "+finalBalance+"p");
			} else if(finalBalance<0){
				System.out.println("You are in debt with StockBroker for the amount of "+finalBalance+"p");
			}
		} catch (final NoSuchAccountException e) {
			System.out.println("ERROR:No such account.");
		} catch (final WrongPINException e) {
			System.out.println("ERROR:Wrong PIN number.");
			// e.printStackTrace();
		}
	}

	private static String getInput() {
		System.out.print(": ");
		final InputStreamReader converter = new InputStreamReader(System.in);
		final BufferedReader in = new BufferedReader(converter);
		String str = null;
		try {
			str = in.readLine();
		} catch (final IOException e) {
			e.printStackTrace();
		}
		System.out.println();
		return str;
	}

	public static void main(String[] args) {
		try {
			if (args.length != 2) {
				printUsage();
				System.exit(1);
			}
			StockBrokerClient.serverAddress = args[0];
			StockBrokerClient.serverRegistryPort = Integer.parseInt(args[1]);

			System.out.println("***Stock Broker CLIENT***");
			try {
				final Registry registry = LocateRegistry.getRegistry(
						StockBrokerClient.serverAddress,
						StockBrokerClient.serverRegistryPort);
				StockBrokerClient.broker = (StockBroker) registry
						.lookup("StockBrokerServer");

				System.out.println("Connected to rmiregistry on "
						+ StockBrokerClient.serverAddress + ":"
						+ StockBrokerClient.serverRegistryPort);
			} catch (RemoteException e) {
				System.out
						.println("CRITICAL ERROR:Cannot connect to the rmiregistry!");
				// e.printStackTrace();
				System.exit(1);
			}
			while (true) {
				printMenu();
				switch (parseInputInt()) {
				case 0:
					System.exit(0);
					break;
				case 1:
					actCreateAccount();
					break;
				case 2:
					actTerminateAccount();
					break;
				case 3:
					actShowRegisteredStock();
					break;
				case 4:
					actShowAccountInfo();
					break;
				case 5:
					actListAllAccounts();
					break;
				case 6:
					actBuyStock();
					break;
				case 7:
					actSellStock();
					break;
				case 8:
					actShowRanking();
					break;
				default:
					System.out.println("Invalid selection");
				}
			}
		} catch (AccessException e) {
			System.out.println("CRITICAL ERROR:Cannot access the registry");
			// e.printStackTrace();
			System.exit(1);
		} catch (RemoteException e) {
			System.out
					.println("CRITICAL ERROR:Cannot connect to the server service!");
			e.printStackTrace();
			System.exit(1);
		} catch (NotBoundException e) {
			System.out
					.println("CRITICAL ERROR:Service not found in the rmiregistry");
			// e.printStackTrace();
			System.exit(1);
		}
	}

	private static void actShowRanking() throws RemoteException {
		StockBrokerAccountInfo[] accountsInfo = broker.listAccountsRanked();
		int i = 1;
		String[][] table = new String[accountsInfo.length + 1][3];
		table[0][0] = "Place";
		table[0][1] = "Acct.Name";
		table[0][2] = "Pot.Balance";
		for (final StockBrokerAccountInfo acct : accountsInfo) {
			table[i][0] = "" + i;
			table[i][1] = "" + acct.getName();
			table[i][2] = "" + acct.getValue();
			i++;
		}
		System.out.println("ACCOUNT RANKING BY CURRENT POTENTIAL BALANCE:\n");
		printTable(table);
	}

	private static int parseInputInt() {
		int i;
		while (true) {
			try {
				final String str = getInput();
				if (str.length() == 0)
					return -1;
				i = Integer.parseInt(str);
				if (i < 0) {
					throw new NumberFormatException();
				}
				break;
			} catch (NumberFormatException e) {
				System.out.println("Invalid input");
				// e.printStackTrace();
			}
		}
		return i;
	}

	private static void printMenu() {
		System.out.println("\n***MAIN MENU***");
		System.out.println("1.Create an account");
		System.out.println("2.Terminate an account");
		System.out.println("3.Show registered stock");
		System.out.println("4.Show account information");
		System.out.println("5.List all accounts information");
		System.out.println("6.Buy stock");
		System.out.println("7.Sell stock");
		System.out.println("8.Show ranking table");
		System.out.println("0.Exit");
	}

	private static void printUsage() {
		System.out
				.println("usage: java StockBrokerClient <serverAddress> <rmiregistryPort>");
	}

	private static String validateInputPIN() {
		while (true) {
			final String PIN = getInput();
			if (PIN.length() == 4)
				try {
					if (Integer.parseInt(PIN) >= 0)
						return PIN;
				} catch (final Exception e) {
				}
			else if (PIN.length() == 0)
				return ""; // Allowing cancelling
			System.out.println("Invalid PIN, please try again");
		}

	}

	public static void printTable(String[][] table) {
		// Find out what the maximum number of columns is in any row
		int maxColumns = 0;
		for (int i = 0; i < table.length; i++) {
			maxColumns = Math.max(table[i].length, maxColumns);
		}

		// Find the maximum length of a string in each column
		int[] lengths = new int[maxColumns];
		for (int i = 0; i < table.length; i++) {
			for (int j = 0; j < table[i].length; j++) {
				lengths[j] = Math.max(table[i][j].length(), lengths[j]);
			}
		}

		// Generate a format string for each column
		String[] formats = new String[lengths.length];
		for (int i = 0; i < lengths.length; i++) {
			formats[i] = "%1$" + lengths[i] + "s"
					+ (i + 1 == lengths.length ? "\n" : " ");
		}

		// Print 'em out
		for (int i = 0; i < table.length; i++) {
			for (int j = 0; j < table[i].length; j++) {
				System.out.printf(formats[j], table[i][j]);
			}
		}
	}
}
